package com.winner.data.config;

import lombok.extern.slf4j.Slf4j;
import org.springframework.aop.interceptor.AsyncUncaughtExceptionHandler;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.task.TaskExecutor;
import org.springframework.scheduling.annotation.AsyncConfigurer;
import org.springframework.scheduling.annotation.EnableAsync;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;

import java.util.concurrent.ThreadPoolExecutor;

// https://blog.csdn.net/yudianxiaoxiao/article/details/107580183
// 如何确定线程池大小？
// 目前确定线程池大小一共有两派，对应着两个不同的公式，但是对错都没有人证明，比较不可控的因素多，具体还是要看自己的业务。

//  计算密集型
//计算密集型，此时系统运作大部分的状况是CPU Loading 100%，CPU要读/写I/O(硬盘/内存)，I/O在很短的时间就可以完成，而CPU还有许多运算要处理，CPU Loading很高。
//比如说要计算1+2+3+…+ 1亿、计算圆周率后几十位、数据分析。都是属于CPU密集型程序。
//在单核CPU情况下，线程池有6个线程，但是同一时间只能运行一个线程，考虑到线程之间还有上下文切换的时间消耗，其实不如单个线程执行高效。所以，单核CPU处理CPU密集型程序，最好不使用多线程
//假如是6核CPU，理论上运行速度可以提升6倍。每个线程都有 CPU 来运行，并不会发生等待 CPU 时间片的情况，也没有线程切换的开销
//所以，多核CPU处理CPU密集型程序才合适，而且中间可能没有线程的上下文切换（一个核心处理一个线程）

// I/O请求不仅仅指的是磁盘读写，在互联网服务中更多指的是网络I/O请求  对于一个Tomcat来说，它处理的大多数都是I/O密集型的业务，可以说是最好的实践场景
// Tomcat默认核心线程数都是200  同一时间可以接受200个请求  ，每个Feign 客户端的线程池大小是 8。这意味着每个 Feign 客户端最多可以并发处理 8 个请求。
//  IO密集型  在我们的业务开发中，基本上都是IO密集型，因为往往都会去操作数据库，访问redis，es等存储型组件，涉及到磁盘IO，网络IO。
// 系统大部分的状况是CPU在等I/O (硬盘/内存) 的读/写操作，但CPU的使用率不高。简单的说，就是需要大量的输入输出，比如读写文件、DB读写、传输文件、网络请求
//此时，系统大部分的状况是CPU在等I/O (硬盘/内存) 的读/写操作，但CPU的使用率不高。简单的说，就是需要大量的输入输出，比如读写文件、DB读写、传输文件、网络请求。


/**
 * https://blog.csdn.net/asd136912/article/details/87716215
 *
 * @EnableAsync表示开启对异步任务的支持，可以放在springboot的启动类上，也可以放在自定义线程池的配置类上 良好的系统是不会不断的创建销毁线程的，而是有线程池进行维护
 * @EnableAsync注解开启异步任务支持(必须加上,否则异步不生效) 这2个注解要结合使用，才能发挥异步的效果
 * @Async可以放在接口处（或者impl上）。但是只有使用的是JDK的动态代理时才有效，CGLIB会失效。因此建议：统一写在实现类的方法上
 * @author wangxinle5
 * @since 2023-01-03
 */
@Slf4j
@Configuration
@EnableAsync
public class ThreadPoolConfig implements AsyncConfigurer {

    /**
     * 线程名称前缀
     */
    private final static String THREAD_NAME_PREFIX = "mybatisplus-service-async";

    /**
     * ThredPoolTaskExcutor的处理流程
     * 当池子大小小于corePoolSize，就新建线程，并处理请求
     * 当池子大小等于corePoolSize，把请求放入workQueue中，池子里的空闲线程就去workQueue中取任务并处理
     * 当workQueue放不下任务时，就新建线程入池，并处理请求，如果池子大小撑到了maximumPoolSize，就用RejectedExecutionHandler来做拒绝处理
     * 当池子的线程数大于corePoolSize时，多余的线程会等待 keepAliveSeconds 长时间，如果无请求可处理就自行销毁
     *
     * 还应该合理设置线程池的大小，避免过度拥塞或资源浪费。
     */
    @Override
    @Bean
    public TaskExecutor getAsyncExecutor() {
        ThreadPoolTaskExecutor taskExecutor = new ThreadPoolTaskExecutor();
        // 初步设置IO密集型线程池大小
        // 设置核心线程数
        // 根据实际情况设置核心线程数和最大线程数   所以很明显java 中 getRuntime().availableProcessors  返回的就是逻辑处理器数
        int corePoolSize = Runtime.getRuntime().availableProcessors() * 2;
        // 如果池中的实际线程数小于corePoolSize,无论是否其中有空闲的线程，都会给新的任务产生新的线程
        taskExecutor.setCorePoolSize(corePoolSize);
        // 常可以将最大线程数设置为核心线程数的2到4倍
        int maxPoolSize = corePoolSize * 2;
        // 设置最大线程数
        taskExecutor.setMaxPoolSize(maxPoolSize);
        // 设置缓冲队列容量 默认是 Integer.MAX_VALUE;
        taskExecutor.setQueueCapacity(100);
        // 设置空闲线程存活时间600秒, 即10分钟
        taskExecutor.setKeepAliveSeconds(600);
        // 设置默认线程名称前缀
        taskExecutor.setThreadNamePrefix(THREAD_NAME_PREFIX);

        // 设置拒绝策略:线程池达到最大线程数,且缓冲队列满后，不再开启新的异步线程，由调用者所在的线程来执行
        // 线程池对拒绝任务(无线程可用)的处理策略
        // 拒绝策略用于控制当线程池已满时，如何处理新提交的任务。以下是一些常见的拒绝策略选项：
//        一共有4中策略, 第一种是默认策, 一般用前面2中, 这里用的是第二种, 对任务的可靠性要求较高，可以选择CallerRunsPolicy，让调用者线程来处理任务；
//        AbortPolicy（默认策略）：如果线程池无法接受新任务，将抛出RejectedExecutionException异常。这是默认的拒绝策略。
//        CallerRunsPolicy：当线程池无法接受新任务时，将把任务退回给调用者，由调用者线程来执行该任务。这可能会导致任务提交线程执行任务，从而降低任务的并行性。
//        DiscardPolicy：当线程池无法接受新任务时，直接丢弃新的任务，不做任何处理。
//        DiscardOldestPolicy：当线程池无法接受新任务时，丢弃任务队列中最旧的任务，然后尝试重新提交新的任务。
        taskExecutor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
        // 等待所有任务结束后再关闭线程池
        taskExecutor.setWaitForTasksToCompleteOnShutdown(true);
        taskExecutor.initialize();
        log.info("thread pool executor initialized");
        return taskExecutor;
    }

    @Override
    public AsyncUncaughtExceptionHandler getAsyncUncaughtExceptionHandler() {
        return AsyncConfigurer.super.getAsyncUncaughtExceptionHandler();
    }
}
